package SSF.Net;

import java.util.*;

import SSF.Util.Random.*;
import SSF.Net.Util.*;
import SSF.OS.*;
import com.renesys.raceway.DML.*;
import com.renesys.raceway.SSF.*;
import com.renesys.raceway.OutOfCore.*;
import java.io.IOException;

/** Top-level modeling class for a network simulation.  One Net serves 
  * as the initializer for the entire simulation; it contains hosts, 
  * routers (which are hosts), and links.  Net also serves as the 
  * central resource for clock granularity information and 
  * translation among NHI, CIDR, and IP addressing schemes. <p> 
  * Net may be run from the command line; use the <b>-help</b> flag to 
  * learn the syntax. 
  */
public class Net extends Entity implements Configurable {

//----------------------------------------------------- CLOCK MANAGEMENT

/** Static clock frequency that all simulation components should use for 
  * reference, for consistency.  One tick of the logical clock equals 
  * 1/F seconds of simulated time.  By default, frequency==10e6; this  
  * yields microsecond simulation resolution.  To avoid clock underflow,
  * this frequency must be set at least as high as the bit rate on the 
  * fastest configured network interface card (NIC).
  */
  public static long frequency = 1000000;

/** Static convenience function for computing number of clock ticks 
  * in a given number of seconds; simply returns s*frequency.  For 
  * example, if Net.frequency is 1000 (millisecond resolution), then 
  * Net.seconds(0.25) returns 250.  
  */
  public static long seconds(double s) {
    return (long)(s*frequency);
  }

//----------------------------------------------------- ADDRESS TRANSLATION

/** Three-way translation table for NHI addresses, CIDR block addresses, 
  * and IP addresses. 
  */
  public cidrBlock cidrMap;

/** Return the IP address corresponding to the given global NHI address */
  public String nhi_to_ip(String naddr) {
    return cidrMap.nhi_to_ip(naddr);
  }

/** Return the global NHI address corresponding to the given IP address */
  public String ip_to_nhi(String ipaddr) {
      int slash = ipaddr.indexOf("/");
	  
      int prefix_len = 
	  (slash<0?32:new Integer(ipaddr.substring(slash+1)).intValue());
	  
      return cidrMap.ip_to_nhi(IP_s.StrToInt(ipaddr),prefix_len);
  }

/** Return the CIDR block address corresponding to the given global 
  * NHI address. 
 */
  public String nhi_to_cidr(String naddr) {
    return cidrMap.nhi_to_cidr(naddr);
  }

//---------------------------------------------------- COMMAND-LINE DRIVER

/** Dump a summary of command-line syntax to standard error. */
  public static void syntax() {
    System.err.println("Syntax: Net [-check]"); 
    System.err.println("            [-dump] ");
    System.err.println("            [-relax] ");
    System.err.println("            [-ooc] ");
    System.err.println("            [-ip a.b.c.d/m] ");
    System.err.println("            maxtime dmlfile1 [ dmlfile2 [...dmlfileN]]]");
    System.err.println();
    System.err.println(" -check: schema-check the input DML file(s) first");
    System.err.println(" -dump: dump NHI/CIDR/IP address tables, but don't actually run the model");
    System.err.println(" -relax: fail to enforce strict rules about CIDR/IP attributes");
    System.err.println(" -ip addr: allocate IP addresses from the given block (default: 0.0.0.0/0)");
    System.exit(-1);
  }

/** 
 * Command line driver for the Net class. <p> 
 *<pre>
 *   Syntax: Net [-check]
 *               [-dump] 
 *               [-relax] 
 *               [-ooc] 
 *               [-ip a.b.c.d/m] 
 *               maxtime dmlfile1 [ dmlfile2 [...dmlfileN]]
 *
 *    -check: schema-check the input DML file(s) first
 *    -dump: dump NHI/CIDR/IP address tables, but don't actually run the model
 *    -relax: fail to enforce strict rules about CIDR/IP attributes
 *    -ip addr: allocate IP addresses from the given block (default: 0.0.0.0/0)
 *</pre>
 */
  public static void main(String[] argv) {
    try{tmain(argv);}catch(Exception x) {x.printStackTrace();System.exit(1);};
  }

  public static void tmain(String[] argv) {
    
    Net N = new Net();
    
    long runTime =0;

    dmlConfig netconfig = null;

    boolean doSchemaCheck = false;
    boolean doCompleteRun = true;

    String ipBase = "0.0.0.0/0";  // override with -ip argument

    int parg = 0;

    try {
      if (argv.length>1 && argv[0].indexOf("-h")!=1){
        
        while (argv.length>parg) {
          if (argv[parg].equals("-relax")) {
            parg++;
            cidrBlock.strict = false;
          }       

          else if (argv[parg].equals("-check")) {
            parg++;
            doSchemaCheck = true;
          }
          
          else if (argv[parg].equals("-dump")) {
            parg++;
            doCompleteRun = false;
          }

          else if (argv[parg].equals("-ooc")) {
            parg++;
            ooc = true;
            try {
              heap = new Heap();
            } catch (IOException e) {
              e.printStackTrace();
              System.exit(1);
            }
          }
          
          else if (argv[parg].equals("-ip")) {
            if (argv.length==parg+1) 
              syntax();
            else
              ipBase = argv[parg+1];
            parg += 2;
          } 

          else if (argv[parg].indexOf("-")==0) 
            syntax();
          
          else break;
        }

        if (argv.length < parg+2) syntax();

        // now argv[parg] is the logical duration, which we use later

        netconfig = new dmlConfig(argv[parg+1]);
        
        for (int n=parg+2; n<argv.length; n++)
          netconfig.load(argv[n]);
        
      } else syntax();
        

      // 0. Schema-check the network configuration.

      if (doSchemaCheck) 
        netconfig.check();
      

      Configuration netcfg = (Configuration)netconfig.findSingle(".Net");
      if (netcfg == null) {
        System.err.println("Configuration database must "+
                           "define a .Net attribute.");
        System.err.println(netconfig);
        System.exit(-1);
      } 

      if (null != netconfig.findSingle(".link") ||
          null != netconfig.findSingle(".router") ||
          null != netconfig.findSingle(".host")) {
        System.err.println("Configuration database may not specify {link, router, host} outside top-level Net");
        System.exit(-1);
      }

      // 1. Construct the address partition map.
      
      N.cidrMap = new cidrBlock(netcfg,ipBase);
      //if (!doCompleteRun) {
        System.out.println();
        N.cidrMap.dump_by_cidr();
        System.out.println();
        N.cidrMap.dump_by_subnet();
        System.out.println();
      //}

      // 2. Configure the network.

      N.config(netcfg);
      
      // 3. Configure the forwarding tables. (optional)
      for (Enumeration nodes = netconfig.find("ftable");
           nodes.hasMoreElements();) {
        Configuration ncfg = (Configuration)nodes.nextElement();
        Host forHost = (Host)N.hosts.get(ncfg.findSingle("id"));
        
        ((IP)forHost.SessionForName("ip")).
          getRoutingTable().config(ncfg);
      }


/*
      // 4. Examine host interconnections.  (debug only)
      for (Enumeration hset = N.hosts.elements();
           hset.hasMoreElements();) 
        ((Host)hset.nextElement()).introspection();
*/

      
       
    } catch (Exception any) {
      any.printStackTrace();
      System.exit(-1);
    }
    
    
    if (doCompleteRun) {
      runTime = seconds((new Double(argv[parg])).doubleValue());
      System.err.println("** Running for "+runTime+" clock ticks (== "+
                         (runTime*1./frequency)+" seconds sim time)");
      N.startAll(0,runTime);
      N.joinAll();
    }

    for (Enumeration finals = N.finalizers.elements();
	 finals.hasMoreElements();) 
	((Runnable)finals.nextElement()).run();
  }

//----------------------------------------------------- OUT-OF-CORE SUPPORT

  public static boolean ooc = false;
  public static Heap heap = null;

//----------------------------------------------------- FINALIZERS

    private Vector finalizers;

    public void wrapup(Runnable finalizer) {
	finalizers.addElement(finalizer);
    }
  
//----------------------------------------------------- CONSTRUCTOR

/** Table mapping NHI addresses (Strings) to Router/Host instances. */ 
  public Hashtable hosts;

  public Net()  {
    hosts = new Hashtable();
    finalizers = new Vector();
  }     

//----------------------------------------------------- NET CONFIGURATION
  private long hct=0,lhct=0;

  /**
  *  Configure a network model.<p>
  *
  *  REQUIRED ATTRIBUTES: none<p>
  *
  *  OBSOLETE ATTRIBUTES: <br>
  *  <ul><li><b>LAN</b>
  *  </ul>
  *
  *  OPTIONAL ATTRIBUTES:<br> 
  *  <ul><li><b>router</b> --- Router configuration  (see Router)
  *      <li><b>host</b>    --- isolated Host configuration (see Host)
  *      <li><b>Net</b>    --- sub-network configuration (recursive)
  *      <li><b>cidr</b>    --- CIDR block number or string (e.g., "1/4/2")
  *      <li><b>frequency</b> --- logical ticks per second of simulated time
  *                                  (long integer) for all network components
  *  </ul><p>
  *
  */
  public void config(Configuration cfg) throws configException {

    long startTime = System.currentTimeMillis();

    String hertz = (String)cfg.findSingle("frequency");
    if (hertz == null) {
      System.err.println("Warning: no frequency specified for Net;");
      System.err.println("\t\t Using default "+(long)(1.e9/frequency)+
                         "ns clock resolution");
    } else {
      frequency = (new Long(hertz)).longValue();
      System.err.println("** \t\t Using specified "+(double)(1.e9/frequency)+
                         "ns clock resolution");
    }


    configRandomStreams((Configuration)cfg.findSingle("randomstream"));


    // Phase 0 -- check for obsolete attributes
    for (Enumeration elans = cfg.find("LAN");
         elans.hasMoreElements();) 
      throw new configException("Net.LAN attribute is obsolete; "+
                                "consult SSFNET 0.7 documentation");


    // Phase I --- construct the table of routers and hosts
    System.err.println("\n\n--- Phase I: construct table of "+
                       "routers and hosts");

    configHostsRouters(cfg);


    // Phase II --- connect all point-to-point links 
    System.err.println("\n\n--- Phase II: connect Point-To-Point links");

    configLinks(cfg);

    // Phase III --- add static routes through connected links
    System.err.println("\n\n--- Phase III: add static routes");
    
    configStaticRoutes();

    long elapsed = System.currentTimeMillis()-startTime;

    System.err.println("## Net config: "+
                       hct+" routers and hosts");

    String etime = (1.*elapsed/1000)+"";
    if (etime.length()>7)
      etime = etime.substring(0,etime.indexOf(".")+3);

    System.err.println("## Elapsed time: "+etime+" seconds");
  }

//------------------------------------------------------ RANDOM DISTRIBUTIONS

  public static final int STREAM_PER_TIMELINE     = 0x0; 
  public static final int STREAM_PER_HOST         = 0x1; 
  public static final int STREAM_PER_PROTOCOL     = 0x2; 
  public static final int STREAM_PER_DISTRIBUTION = 0x3; 

  protected static int sharingLevel;
  protected static String streamName;
  protected static String generatorName;

  public void configRandomStreams(Configuration cfg) throws configException {
    String replevel = null;

    if (cfg!=null) {
      generatorName = (String)cfg.findSingle("generator");
      streamName = (String)cfg.findSingle("stream");
      replevel = (String)cfg.findSingle("reproducibility_level");
    }

    if (generatorName==null) generatorName = "MersenneTwister";  
    if (streamName==null) streamName = "DefaultStream";
    if (replevel==null) replevel = "timeline";

    if ("timeline".equals(replevel))
      sharingLevel = STREAM_PER_TIMELINE;
    else if ("host".equals(replevel)) 
      sharingLevel = STREAM_PER_HOST;
    else if ("protocol".equals(replevel)) 
      sharingLevel = STREAM_PER_PROTOCOL;
    else if ("distribution".equals(replevel))
      sharingLevel = STREAM_PER_DISTRIBUTION;
    else 
      throw new configException("Bad randomstream.reproducibility_level: "+
                                replevel);
    
  }

  /** Returns a reference to a random distributrution that will be shared
   *  by consumers depending the value of .Net.randomstream.reproducibility_level
   *  Specification of the RandomDistribution is via a Configuration.
   */
  public static synchronized RandomDistribution 
    accessRandomDistribution(ProtocolSession forProtocol,
                             Configuration distcfg, String requestName) {

    RandomDistribution return_dist = null;

    String use_stream_name = streamName;
    String request = requestName;

    // Extend the stream name with disambiguating information 
    // if the current "sharing level" requires that distributions,
    // protocols, hosts, or timelines receive distinct streams.

    switch(sharingLevel) {

    // user-provided distribution instance identifier
    case STREAM_PER_DISTRIBUTION:
      use_stream_name = use_stream_name + "/" + request;
      // fall through to subsequent case

    // class name
    case STREAM_PER_PROTOCOL:
      use_stream_name = use_stream_name + "/" + 
        forProtocol.getClass().getName();
      // fall through to subsequent case

    // host's unique NH address
    case STREAM_PER_HOST:
      use_stream_name = use_stream_name + "/" + 
        ((SSF.Net.Host)forProtocol.inGraph()).nhi;
      break;   // 3 cases above must not append timeline String, bugfix 11/5/00 ato
    
    // string of the form "{Scheduler 2}"
    case STREAM_PER_TIMELINE:
      use_stream_name = use_stream_name + "/" + 
      forProtocol.inGraph().alignment().toString();

    default:
      break;
    }
    try {
      RandomStream rng = 
        RandomStream.getStream(forProtocol.inGraph(),
                               generatorName,
                               use_stream_name);
      
      return_dist = 
        RandomStream.getDistribution(distcfg,rng);

    } catch (RandomStream.StreamException rngex) {
      rngex.printStackTrace();
      System.exit(-1);
    }
    return return_dist;
  }

  /** Returns a reference to a random distributrution that will be shared
   *  by consumers depending the value of .Net.randomstream.reproducibility_level
   *  Specification of the RandomDistribution is by a name recognized by
   *  class method RandomStream.getDistribution(); the returned handle 
   *  is for default values of the distribution's parameters. The usage
   *  including user-provided distribution parameters may be, from within
   *  an instance of a ProtocolSession ('this'):
   *  <pre>
   *  import cern.jet.random.*;
   *  RandomDistribution rnd = Net.accessRandomDistribution(this, "Uniform", "foo");
   *  ((Uniform)rnd.getImplementation()).setState(0.,1.); // go under the covers
   *  for (int x=0; x<12000; x++) next_sample = rnd.nextDouble();  // etc.
   *  </pre>  
   */
  public static synchronized RandomDistribution 
    accessRandomDistribution(ProtocolSession forProtocol,
                             String distName, String requestName) {

    RandomDistribution return_dist = null;

    String use_stream_name = streamName;
    String request = requestName;

    // Extend the stream name with disambiguating information 
    // if the current "sharing level" requires that distributions,
    // protocols, hosts, or timelines receive distinct streams.

    switch(sharingLevel) {

    // user-provided distribution instance identifier
    case STREAM_PER_DISTRIBUTION:
      use_stream_name = use_stream_name + "/" + request;
      // fall through to subsequent case

    // class name
    case STREAM_PER_PROTOCOL:
      use_stream_name = use_stream_name + "/" + 
        forProtocol.getClass().getName();
      // fall through to subsequent case

    // host's unique NH address
    case STREAM_PER_HOST:
      use_stream_name = use_stream_name + "/" + 
        ((SSF.Net.Host)forProtocol.inGraph()).nhi;
      break;   // 3 cases above must not append timeline String, bugfix 11/5/00 ato

    // string of the form "{Scheduler 2}"
    case STREAM_PER_TIMELINE:
      use_stream_name = use_stream_name + "/" + 
      forProtocol.inGraph().alignment().toString();

    default:
      break;
    }
    try {
      RandomStream rng = 
        RandomStream.getStream(forProtocol.inGraph(),
                               generatorName,
                               use_stream_name);
      
      return_dist = 
        RandomStream.getDistribution(distName,rng);

    } catch (RandomStream.StreamException rngex) {
      rngex.printStackTrace();
      System.exit(-1);
    }
    return return_dist;
  }

  /** Returns a reference to a raw random number generator output specified
   *  by the global attribute .Net.randomstream; that will be shared
   *  by consumers depending the value of 
   *  .Net.randomstream.reproducibility_level.
   *  If .Net.randomstream is not provided, the default is:
   * <pre>
   *  generatorName = "MersenneTwister" 
   *  stream = "DefaultStream"  // root for the initial seed values, via MD5
   *  reproducibility_level  = "timeline"
   *
   * usage:
   *
   *  RandomStream rng = Net.accessRandomStream(this, "foo");
   *  for (int x=0; x<1000; x++) next_sample = rng.nextDouble();
   *  </pre>  
   */
  public static synchronized RandomStream 
    accessRandomStream(ProtocolSession forProtocol, String requestName) {

    RandomStream rng = null;
    String use_stream_name = streamName;
    String request = requestName;

    // Extend the stream name with disambiguating information 
    // if the current "sharing level" requires that
    // protocols, hosts, or timelines receive distinct streams.

    switch(sharingLevel) {

    // append user-provided stream instance identifier
    case STREAM_PER_DISTRIBUTION:
      use_stream_name = use_stream_name + "/" + request;
      // fall through to subsequent case

    // append class name
    case STREAM_PER_PROTOCOL:
      use_stream_name = use_stream_name + "/" + 
                       forProtocol.getClass().getName();
      // fall through to subsequent case

    // append host's unique NH address
    case STREAM_PER_HOST:
      use_stream_name = use_stream_name + "/" + 
              ((SSF.Net.Host)forProtocol.inGraph()).nhi;
      break;   // 3 cases above must not append timeline String, bugfix 11/5/00 ato

    // append string of the form "{Scheduler 2}"
    case STREAM_PER_TIMELINE:
      use_stream_name = use_stream_name + "/" + 
           forProtocol.inGraph().alignment().toString();
      break;

    default:
      break;
    }
    try {
      rng = 
        RandomStream.getStream(forProtocol.inGraph(),
                               generatorName,
                               use_stream_name);
    } catch (RandomStream.StreamException rngex) {
      rngex.printStackTrace();
      System.exit(-1);
    }
    return rng;
  }

//------------------------------------------------------ HOST,ROUTER CONFIG

/** Configure the hosts and routers within the network. */
  private void configHostsRouters(Configuration cfg) throws configException {
    configHostsRouters(cfg,"",null);
  }

  private void configHostsRouters(Configuration cfg, String in_nhi,
                                  String default_timeline) 
    throws configException {
      
      //System.err.println("Configuring Net "+newnhi+"...");
      
      String overriding_timeline = (String)cfg.findSingle("alignment");
      
      if (overriding_timeline!=null) 
        default_timeline = overriding_timeline;
      
      for (Enumeration enets = cfg.find("Net");
           enets.hasMoreElements();) {
        Configuration ncfg = (Configuration)enets.nextElement();

        if (null != ncfg.findSingle("randomstream")) 
          throw new configException 
            ("Attribute Net.randomstream may only appear at top level");

        idrange netids = new idrange(); netids.config(ncfg);
        for (int netid=netids.minid;netid<=netids.maxid;netid++)
          configHostsRouters(ncfg,
                             (("".equals(in_nhi))?
                              (""+netid):
                              (in_nhi+cidrBlock.NHI_SEPARATOR+netid)),
                             default_timeline);
      }
      
      for (Enumeration erouters = cfg.find("router"); 
           erouters.hasMoreElements();) {
        Configuration rcfg = (Configuration)erouters.nextElement();
        idrange ids = new idrange(); ids.config(rcfg);
        for (int id = ids.minid; id<=ids.maxid; id++)
          config_new_router_or_host(new Router(this,in_nhi,id), 
                                    rcfg,default_timeline);
      }
      
      for (Enumeration ehosts = cfg.find("host"); 
           ehosts.hasMoreElements();) {
        Configuration hcfg = (Configuration)ehosts.nextElement();
        idrange ids = new idrange(); ids.config(hcfg);
        for (int id = ids.minid; id<=ids.maxid; id++)
          config_new_router_or_host(new Host(this,in_nhi,id), 
                                    hcfg,default_timeline);
      }
    }


  private void config_new_router_or_host(Host H, Configuration hcfg,String default_timeline) 
    throws configException {
      if (default_timeline!=null)
        configTimeline(H,default_timeline);

      else 
        H.alignTo(this); // default alignment to containing Net

      H.config(hcfg);  // may override alignment via "alignment" attribute
      hosts.put(H.nhi, H);
      hct++;
    }

//------------------------------------------------------ TIMELINE CONFIG

  /** Helper function to let Hosts, Routers, Links set their alignments 
    * consistently from their respective config files, which share 
    * a common optional "alignment" attribute.  The values associated 
    * with that attribute are resolved globally, so that any two 
    * Hosts, Routers etc. that specify the same integer or string
    * value will get the same alignment (and will therefore execute on 
    * the same processor).   Alignments may also be specified for Net
    * attributes, in which case all hosts, routers, and links within the Net 
    * get that alignment by default. <p>
    * The default is to omit all "alignment" attributes, resulting 
    * in serial (single timeline) execution.<p>
    */
  public static void configTimeline(Entity forEntity, Configuration cfg) {

    if (timelineMap == null) timelineMap = new Hashtable();

    try {
      if (cfg.findSingle("timeline")!= null) 
        throw new configException
          ("The 'timeline' attribute is obsolete; use 'alignment' instead");
      
      Enumeration timelinespec = cfg.find("alignment");
      
      if (timelinespec.hasMoreElements())
        configTimeline(forEntity,(String)timelinespec.nextElement());
    }
    catch (configException cfgex) {
      cfgex.printStackTrace(); // otherwise ignore it
    }
  }
  private static void configTimeline(Entity forEntity, String alignspec) {
    if (timelineMap == null) timelineMap = new Hashtable();
    Entity alignmentTarget = (Entity)timelineMap.get(alignspec);
    
    if (alignmentTarget == null) {  
      timelineMap.put(alignspec,forEntity);
      forEntity.makeIndependent();
    }
    else forEntity.alignTo(alignmentTarget); 
  }
  private static Hashtable timelineMap;

//------------------------------------------------------ LINK CONFIG


  private void configLinks(Configuration cfg) throws configException {
    configLinks(cfg,"",null);
  }
  
  private void configLinks(Configuration cfg, String in_nhi,
                           String default_timeline) 
    throws configException {

      String overriding_timeline = (String)cfg.findSingle("alignment");
      
      if (overriding_timeline!=null) 
        default_timeline = overriding_timeline;
      
      for (Enumeration enets = cfg.find("Net");
           enets.hasMoreElements();) {
        Configuration ncfg = (Configuration)enets.nextElement();
        idrange netids = new idrange(); netids.config(ncfg);
        for (int netid=netids.minid;netid<=netids.maxid;netid++)
          configLinks(ncfg,(in_nhi.equals("")?""+netid:in_nhi+":"+netid),
                      default_timeline);
      }
      
      for (Enumeration elinks = cfg.find("link");
           elinks.hasMoreElements();) 
        config_new_link(new link(this,in_nhi),
                        (Configuration)elinks.nextElement(),
                        default_timeline);
    }

  private void config_new_link(link L, Configuration lcfg,
                               String default_timeline) 
    throws configException {
      if (default_timeline!=null)
        configTimeline(L,default_timeline);

      else 
        L.alignTo(this); // default alignment to containing Net

      L.config(lcfg);  // may override alignment via "alignment" attribute
    }

//------------------------------------------------------ STATIC ROUTE CONFIG

  private void configStaticRoutes() throws configException {
    for (Enumeration hset= hosts.elements(); 
         hset.hasMoreElements();) {
      try {
        Host H = (Host)hset.nextElement();
        IP IPsess = (IP)H.SessionForName("ip");
        RadixTreeRoutingTable RT = (RadixTreeRoutingTable)IPsess.getRoutingTable();
        RT.config(H.hostConfig); // add static routes via "route" attribute

      } catch (ProtocolException pex) {
        // ignore if sending host does not implement IP
      }
    }
  }
}

/*=                                                                      =*/
/*=  Copyright (c) 1997--2000  SSF Research Network                      =*/
/*=                                                                      =*/
/*=  SSFNet is open source software, distributed under the GNU General   =*/
/*=  Public License.  See the file COPYING in the 'doc' subdirectory of  =*/
/*=  the SSFNet distribution, or http://www.fsf.org/copyleft/gpl.html    =*/
/*=                                                                      =*/

